POI2014 Salad Bar
题目大意:
有一个长度为n的字符串,每一位只会是p或j。你需要取出一个子串S(从左到右或从右到左一个一个取出),使得不管是从左到右还是从右往左取,都保证每时每刻已取出的p的个数不小于j的个数。你需要最大化|S|。
题解:
首先很容易想到把每一个p看做1,把每一个j看做−1,之后通过前缀和(sum)来判断是否满足条件。
如果一个子串[L,R]是合法的,那么必然满足以下条件:
∀k∈[L+1,R],sumi≤sumk≤sumj
反之,只要满足了上述条件,这个子串一定合法。
于是,我们不难想到,枚举一个每一个L,对于每一个L,向后找R。
对于一个L, 找L右边第一个sum比i小的下标k。
那么显然,k以及k右边的点,都无法做i的右端点。
因而只能在区间[L+1,k−1]找右端点。
因为要使sumL≤sumx≤sumR,显然区间中sum最大的哪一个即为右端点。(若最大有多个,取最右边的那个。)
这个可以用反证法:
假设我们不以最大的为右端点。
那么有两种情况:
- 最大的点在你取的点的左边,那么这样最大的点在[L+1,R]之间,但是sum值却比R 大,显然是不合法的。
- 最大的点在你取的点的右边。那么我们取最大的点一定可以满足,并且还比当前点更优,因而还不如取最大的那个。
综上所述,我们得到了一种复杂度为O(nlogn)的算法。
首先可以O(n) 预处理出每一个左端点的右端点存在的可能区间。
之后对于每一个左端点,我们在区间查询最大值即可。(这个可以用线段树等数据结构来写,单次查询为O(logn))。
但实际上还有一种O(n)的写法:
不妨把问题具象化:
把前缀和看做是高度,由于每一次更改的值绝对值为1 ,因而一个个sum连成了一幅连续的折线图。
我们把向上凸起的称为山峰,向下凹的称为山谷。
那么对于每一个点,我们要找的实际上是,往右延伸时,在不出现比当前点高度矮的山谷的前提下,最高的山峰的位置。
这个可以用dp来写。
- 若stri=p ,那么我们有 peaki={peaki+1(sumpeaki+1>sumpeaknxti)peaknxti(sumpeaki+1≤sumpeaknxti)
- 若stri=j ,那么我们有peak i=i
其中nxti表示,下一个出现的与当前点高度相同的点的位置。
我们可以O(n)预处理出nxt数组,又可以O(n)实现dp值的转移。
综上题目即可解。
Code:
这里只给出O(n)的代码:
|
|